-- 2022-8-15

---[[ 17.1 函数require

    -- 尽管函数require也只是一个没什么特殊之处的普通函数 但在Lua语言的模块实现中扮演着核心角色
    -- 要加载模块时 只需要简单的调用这个函数 然后传入模块名作为参数
    -- 请记住 当函数的参数只有一个字符串常量时括号是可以省略的 而且一般在使用require时按照惯例也会省略括号
    -- 不过尽管如此 下面的这些用法也都是正确的
    local m = require('math')
    local modname = 'math'
    local m = require((modname))
    -- 函数require尝试对模块的定义做最小的假设 对于该函数来说 一个模块可以是定义了一些变量(比如函数或者包含函数的表)的代码
    -- 典型的 这些代码返回一个由模块中函数组成的表
    -- 不过 由于这个动作是由模块的代码而不是由函数require完成的 所以某些模块可能会选择返回其他的值或者甚至引发副作用(例如 通过创建全局变量)

    -- 首先 函数require在表package.loaded中检查模块是否已被加载 如果模块已经被加载 函数返回相应的值
    -- 因此 一旦一个模块被加载过 后续的对于同一模块的所有require调用都将返回同一个值 而不会再运行任何代码

    -- 如果模块尚未加载 那么函数require则搜索具有指定模块名的Lua文件(搜索路径由变量package.path指定 我们会在后续对其进行讨论)
    -- 如果函数require找到了相应的文件 那么就用函数loadfile将其进行加载 结果是一个我们称之为加载器(loader)的函数(加载器就是一个被调用时加载模块的函数)

    -- 如果函数require找不到指定模块名的Lua文件 那么它就搜索相应名称的C标准库
    -- (在这种情况下 搜索路径由变量package.cpath指定)
    -- 如果找到了一个C标准库 则使用底层函数package.loadlib进行加载 这个底层函数会查找名为luaopen_modname的函数
    -- 在这种情况下 加载函数就是loadlib的执行结果 也就是一个被表示为Lua函数的C语言函数luaopen_modname

    -- 不管模块是在Lua文件还是C标准库中找到的 函数require此时都具有了用于加载它的加载函数
    -- 为了最终加载模块 函数require带着两个参数调用加载函数 模块名和加载函数所在文件的名称(大多数模块会忽略这两个参数)
    -- 如果加载函数有返回值 那么函数require会返回这个值 然后将其保存在表package.loaded中 以便于将来在加载同一模块时返回相同的值
    -- 如果加载函数没有返回值且表中的package.loaded[@rep{modname}]为空 函数require就假设模块的返回值是true
    -- 如果没有这种补偿 那么后续调用函数require时将重复加载模块

    -- 要强制函数require加载同一模块两次 可以先将模块从package.loaded中删除:
    package.loaded.modname = nil
    -- 下一次再加载这个模块时 函数require就会重新加载模块
    -- 对于函数require来说 一个常见的抱怨是它不能给待加载的模块传递函数
    -- 例如 数学模块可以对角度和弧度的选择增加一个选项
        -- 错误的代码
        local math = require("math","degree")
    -- 这里的问题在于 函数require的主要目标之一就是避免重复加载模块 
    -- 一旦一个模块被加载 该模块就会在后续所有调用require的程序部分被复用
    -- 这样 不同参数的同名模块之间就会产生冲突
    -- 如果读者真的需要具有参数的模块 那么最好使用一个显式的函数来设置参数
    local mod = require "mod"
    mod.init(0,0)
    -- 如果加载函数返回的是模块本身 那么还可以写成
    local mod = require "mod".init(0,0)
    -- 请记住 模块在任何情况下只加载一次 至于如何处理冲突的加载 取决于模块自己
--]]